#include <stdio.h>
#include <math.h>
#include <gmp.h>
#include <time.h>
#include <pthread.h>
#include <stdlib.h>

/* 10.000.000 de dígitos, em bits  33219281*/
#define bits_precisao 33219281

/* Variáveis globais para comparar o erro */
mpf_t erro_atual, erro_maximo;

/*Declaracao das variaveis usadas no programa*/
mpf_t a0, a1, a2, b0, b1, b2, t0, t1, p0, p1, pi, troca_contexto, aux;


void *calcula_a(void *param);
void *calcula_b(void *param);
void *calcula_t(void *param);
void *calcula_p(void *param);

/* Funcao utilizacao para pegar o tempo atual do relogio. */
double tempo()
{
	struct timeval tv;
	gettimeofday(&tv,0);
	return tv.tv_sec + tv.tv_usec/1e6;
}

/* Calcula a precisão do valor de pi calculado */
int precisao(mpf_t pi_atual, mpf_t pi_aproximado){

	mpf_sub(erro_atual, pi_atual, pi_aproximado);
	mpf_abs(erro_atual, erro_atual);
	if(mpf_cmp(erro_atual, erro_maximo) < 0){		
		return 1;
	}
	return 0;
}


int iteracao = 0;

int main(void){

	FILE *output_pi_calculado, *input_pi_aproximado;

	/* Variáveis para registrar o tempo */
	double tempo_inicio, tempo_fim;


	mpf_t pi_aproximado;
	
	/*Seto a precisão padrao para as variaveis a serem inicializadas*/
	mpf_set_default_prec( bits_precisao );

	/*Declaracao das 4 threads*/
	pthread_t thread_a, thread_b, thread_p, thread_t;

	/*Inicializa as variaveis*/
	mpf_init(a0);
	mpf_init(a1);
	mpf_init(a2);
	mpf_init(b0);
	mpf_init(b1);
	mpf_init(b2);
	mpf_init(t0);
	mpf_init(t1);
	mpf_init(p0);
	mpf_init(p1);
	mpf_init(aux);
	mpf_init(troca_contexto);
	mpf_init(pi);
	mpf_init(pi_aproximado);

	/* Inicializa as variáveis de erro */
	mpf_init(erro_atual);
	mpf_init(erro_maximo);

	/* Carrega o valor de pi calculado, do arquivo */
	input_pi_aproximado = fopen("pi_aproximado.txt", "r");	
	mpf_inp_str(pi_aproximado, input_pi_aproximado, 0);
	fclose(input_pi_aproximado);

	/* Valor do erro máximo, ou seja, número de dígitos corretos*/
	mpf_set_str(erro_maximo, "1e-10000000", 0);

	/* Registra o tempo inicial */
	tempo_inicio = tempo();

	/*Setando as variaveis do algoritmo para comecar a calcular*/
	/*setando a0*/
	mpf_set_ui(a0, 1.0);

	/*setando b0*/
	mpf_sqrt_ui(b0, 2.0);
	mpf_ui_div(b0, 1.0, b0);


	/*setando t0*/
	mpf_set_ui(t0, 1.0);
	mpf_div_ui(t0, t0, 4.0);

	/*setando p0*/
	mpf_set_ui(p0, 1.0);

	/*calculando a1*/
	mpf_add(a1, a0, b0);
	mpf_div_ui(a1, a1, 2.0);

	/*calculando b1*/
	mpf_mul(b1, a0, b0);
	mpf_sqrt(b1, b1);


	/*Iterações de calculo do pi*/
	while(1){
		
		/* Cria as 4 thread para executarem em paralelo. */
		pthread_create (&thread_a, NULL, calcula_a, NULL);
		pthread_create (&thread_b, NULL, calcula_b, NULL);
		pthread_create (&thread_t, NULL, calcula_t, NULL);
		pthread_create (&thread_p, NULL, calcula_p, NULL);
		

		/*Calculando pi*/
		mpf_add(pi, a0, b0);
		mpf_pow_ui(pi, pi, 2.0);
		mpf_mul_ui(aux, t0, 4.0);
		mpf_div(pi, pi, aux);

		/*verifica se atingiu a precisao*/
		if(precisao(pi, pi_aproximado))
			break;

		/*Espero o termino de execucao das threads*/
		pthread_join(thread_p, NULL);
		pthread_join(thread_a, NULL);
		pthread_join(thread_b, NULL);
		pthread_join(thread_t, NULL);
		
		/*Atualiza numero de iteracoes*/
		iteracao ++;

		/* Troco o contexto para preparar para proxima iteracao. */
		*troca_contexto = *a0;
		*a0 = *a1;
		*a1 = *a2;
		*a2 = *troca_contexto;

		*troca_contexto = *b0;
		*b0 = *b1;
		*b1 = *b2;
		*b2 = *troca_contexto;

		*troca_contexto = *t0;
		*t0 = *t1;
		*t1 = *troca_contexto;

		*troca_contexto = *p0;
		*p0 = *p1;
		*p1 = *troca_contexto;


	}
	
	/* Registra o tempo final */
	tempo_fim = tempo();


	/*Arquivo onde será salvo o resultado*/
	output_pi_calculado = fopen("gauss_paralelo.txt", "a+");

	printf("Gauss Legendre Paralelo executado com sucesso\n");
	
	/* Salva informacoes sobre a execucao em um arquivo. */
	fprintf(output_pi_calculado, "\n--------------------------------------------------------\n");
	fprintf(output_pi_calculado, " Gauss Legendre executado com sucesso.\n");
	fprintf(output_pi_calculado, " Os primeiros 30 digitos do resultado sao : ");
	mpf_out_str(output_pi_calculado, 10, 30, pi);
	fprintf(output_pi_calculado, "\n O tempo gasto com a execucao foi de %lf segundos.\n", tempo_fim - tempo_inicio);
	fprintf(output_pi_calculado, " O numero de iteracoes necessarias foi %d.\n", iteracao);
	

	fclose(output_pi_calculado);



	return 0;
}

/*Funcao da thread responsavel por calcular An+2*/
void *calcula_a(void *param){

		/*Calculando a2*/
		mpf_add(a2, a1, b1);
		mpf_div_ui(a2, a2, 2.0);

}

/*Funcao da thread responsavel por calcular Bn+2*/
void *calcula_b(void *param){
		
		/*Calculando b2*/
		mpf_mul(b2, a1, b1);
		mpf_sqrt(b2, b2);


}

/*Funcao da thread responsavel por calcular Tn+1*/
void *calcula_t(void *param){

		/*Calculando t1*/
		mpf_sub(t1, a0, a1);
		mpf_pow_ui(t1, t1, 2.0);
		mpf_mul(t1, t1, p0);
		mpf_sub(t1, t0, t1);

}

/*Funcao da thread responsavel por calcular Pn+1*/
void *calcula_p(void *param){

		/*Calculando p1*/
		mpf_mul_ui(p1, p0, 2.0);

}

